{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modeling and Simulation in Python\n",
    "\n",
    "Chapter 4\n",
    "\n",
    "Copyright 2017 Allen Downey\n",
    "\n",
    "License: [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configure Jupyter so figures appear in the notebook\n",
    "%matplotlib inline\n",
    "\n",
    "# Configure Jupyter to display the assigned value after an assignment\n",
    "%config InteractiveShell.ast_node_interactivity='last_expr_or_assign'\n",
    "\n",
    "# import functions from the modsim library\n",
    "from modsim import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Returning values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a simple function that returns a value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_five(x):\n",
    "    return x + 5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's how we call it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = add_five(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you run a function on the last line of a cell, Jupyter displays the result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "add_five(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But that can be a bad habit, because usually if you call a function and don't assign the result in a variable, the result gets discarded.\n",
    "\n",
    "In the following example, Jupyter shows the second result, but the first result just disappears."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "add_five(3)\n",
    "add_five(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When you call a function that returns a variable, it is generally a good idea to assign the result to a variable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "y1 = add_five(3)\n",
    "y2 = add_five(5)\n",
    "\n",
    "print(y1, y2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function called `make_state` that creates a `State` object with the state variables `olin=10` and `wellesley=2`, and then returns the new `State` object.\n",
    "\n",
    "Write a line of code that calls `make_state` and assigns the result to a variable named `init`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running simulations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the code from the previous notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def step(state, p1, p2):\n",
    "    \"\"\"Simulate one minute of time.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    p1: probability of an Olin->Wellesley customer arrival\n",
    "    p2: probability of a Wellesley->Olin customer arrival\n",
    "    \"\"\"\n",
    "    if flip(p1):\n",
    "        bike_to_wellesley(state)\n",
    "    \n",
    "    if flip(p2):\n",
    "        bike_to_olin(state)\n",
    "        \n",
    "def bike_to_wellesley(state):\n",
    "    \"\"\"Move one bike from Olin to Wellesley.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    \"\"\"\n",
    "    if state.olin == 0:\n",
    "        state.olin_empty += 1\n",
    "        return\n",
    "    state.olin -= 1\n",
    "    state.wellesley += 1\n",
    "    \n",
    "def bike_to_olin(state):\n",
    "    \"\"\"Move one bike from Wellesley to Olin.\n",
    "    \n",
    "    state: bikeshare State object\n",
    "    \"\"\"\n",
    "    if state.wellesley == 0:\n",
    "        state.wellesley_empty += 1\n",
    "        return\n",
    "    state.wellesley -= 1\n",
    "    state.olin += 1\n",
    "    \n",
    "def decorate_bikeshare():\n",
    "    \"\"\"Add a title and label the axes.\"\"\"\n",
    "    decorate(title='Olin-Wellesley Bikeshare',\n",
    "             xlabel='Time step (min)', \n",
    "             ylabel='Number of bikes')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a modified version of `run_simulation` that creates a `State` object, runs the simulation, and returns the `State` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_simulation(p1, p2, num_steps):\n",
    "    \"\"\"Simulate the given number of time steps.\n",
    "    \n",
    "    p1: probability of an Olin->Wellesley customer arrival\n",
    "    p2: probability of a Wellesley->Olin customer arrival\n",
    "    num_steps: number of time steps\n",
    "    \"\"\"\n",
    "    state = State(olin=10, wellesley=2, \n",
    "                  olin_empty=0, wellesley_empty=0)\n",
    "                    \n",
    "    for i in range(num_steps):\n",
    "        step(state, p1, p2)\n",
    "        \n",
    "    return state"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now `run_simulation` doesn't plot anything:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = run_simulation(0.4, 0.2, 60)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But after the simulation, we can read the metrics from the `State` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "state.olin_empty"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can run simulations with different values for the parameters.  When `p1` is small, we probably don't run out of bikes at Olin."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = run_simulation(0.2, 0.2, 60)\n",
    "state.olin_empty"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When `p1` is large, we probably do."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = run_simulation(0.6, 0.2, 60)\n",
    "state.olin_empty"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## More for loops"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`linspace` creates a NumPy array of equally spaced numbers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "p1_array = linspace(0, 1, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use an array in a `for` loop, like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "for p1 in p1_array:\n",
    "    print(p1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will come in handy in the next section.\n",
    "\n",
    "`linspace` is defined in `modsim.py`.  You can get the documentation using `help`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "help(linspace)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`linspace` is based on a NumPy function with the same name.  [Click here](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html) to read more about how to use it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** \n",
    "Use `linspace` to make an array of 10 equally spaced numbers from 1 to 10 (including both)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** The `modsim` library provides a related function called `linrange`.  You can view the documentation by running the following cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "help(linrange)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `linrange` to make an array of numbers from 1 to 11 with a step size of 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sweeping parameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`p1_array` contains a range of values for `p1`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "p2 = 0.2\n",
    "num_steps = 60\n",
    "p1_array = linspace(0, 1, 11)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following loop runs a simulation for each value of `p1` in `p1_array`; after each simulation, it prints the number of unhappy customers at the Olin station:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "for p1 in p1_array:\n",
    "    state = run_simulation(p1, p2, num_steps)\n",
    "    print(p1, state.olin_empty)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can do the same thing, but storing the results in a `SweepSeries` instead of printing them.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "sweep = SweepSeries()\n",
    "\n",
    "for p1 in p1_array:\n",
    "    state = run_simulation(p1, p2, num_steps)\n",
    "    sweep[p1] = state.olin_empty"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then we can plot the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot(sweep, label='Olin')\n",
    "\n",
    "decorate(title='Olin-Wellesley Bikeshare',\n",
    "         xlabel='Arrival rate at Olin (p1 in customers/min)', \n",
    "         ylabel='Number of unhappy customers')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercises\n",
    "\n",
    "**Exercise:** Wrap this code in a function named `sweep_p1` that takes an array called `p1_array` as a parameter.  It should create a new `SweepSeries`, run a simulation for each value of `p1` in `p1_array`, store the results in the `SweepSeries`, and return the `SweepSeries`.\n",
    "\n",
    "Use your function to plot the number of unhappy customers at Olin as a function of `p1`.  Label the axes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function called `sweep_p2` that runs simulations with `p1=0.5` and a range of values for `p2`.  It should store the results in a `SweepSeries` and return the `SweepSeries`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optional Exercises\n",
    "\n",
    "The following two exercises are a little more challenging.  If you are comfortable with what you have learned so far, you should give them a try.  If you feel like you have your hands full, you might want to skip them for now.\n",
    "\n",
    "**Exercise:** Because our simulations are random, the results vary from one run to another, and the results of a parameter sweep tend to be noisy.  We can get a clearer picture of the relationship between a parameter and a metric by running multiple simulations with the same parameter and taking the average of the results.\n",
    "\n",
    "Write a function called `run_multiple_simulations` that takes as parameters `p1`, `p2`, `num_steps`, and `num_runs`.\n",
    "\n",
    "`num_runs` specifies how many times it should call `run_simulation`.\n",
    "\n",
    "After each run, it should store the total number of unhappy customers (at Olin or Wellesley) in a `TimeSeries`.  At the end, it should return the `TimeSeries`.\n",
    "\n",
    "Test your function with parameters\n",
    "\n",
    "```\n",
    "p1 = 0.3\n",
    "p2 = 0.3\n",
    "num_steps = 60\n",
    "num_runs = 10\n",
    "```\n",
    "\n",
    "Display the resulting `TimeSeries` and use the `mean` function provided by the `TimeSeries` object to compute the average number of unhappy customers (see Section 2.7)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:**  Continuting the previous exercise, use `run_multiple_simulations` to run simulations with a range of values for `p1` and\n",
    "\n",
    "```\n",
    "p2 = 0.3\n",
    "num_steps = 60\n",
    "num_runs = 20\n",
    "```\n",
    "\n",
    "Store the results in a `SweepSeries`, then plot the average number of unhappy customers as a function of `p1`.  Label the axes.\n",
    "\n",
    "What value of `p1` minimizes the average number of unhappy customers?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
